package SPA;

import Lexical.Tag;
import Lexical.Token;
import Util.Pair;
import Util.Stack;
import Util.HashTable;

/**
 * DFA State
 */
public class DFAState 
{
	private int id;
	private boolean isFinal;
	private HashTable stateTransitions = new HashTable();
	private HashTable submachineCalls = new HashTable();
	
	public int getId() { return id; }
	public boolean isFinal() { return isFinal; }
	
	/**
	 * Constructor
	 * @param id		state identifier (name)
	 * @param isFinal	if it's final state
	 */
	public DFAState (int id, boolean isFinal)
	{
		this.id = id;
		this.isFinal = isFinal;
	}
	
	/**
	 * Constructor
	 * @param id		state identifier (name)
	 */
	public DFAState (int id)
	{
		this.id = id;
		this.isFinal = false;
	}
	
	/**
	 * Set Final State
	 * @param isFinal
	 */
	public void setFinal(boolean isFinal)
	{
		this.isFinal = isFinal;
	}
	
	public StateTransition[] getStateTransitions()
	{
		Object[] obj = this.stateTransitions.getAll();
		StateTransition[] transitions = new StateTransition[obj.length];
		for (int i = 0; i < obj.length; i++)
		{
			transitions[i] = (StateTransition)obj[i];
		}
		
		return transitions;
	}
	
	public SubmachineCallTransition[] getSubmachineCalls()
	{
		Object[] obj = this.submachineCalls.getAll();
		SubmachineCallTransition[] transitions = new SubmachineCallTransition[obj.length];
		for (int i = 0; i < obj.length; i++)
		{
			transitions[i] = (SubmachineCallTransition)obj[i];
		}
		
		return transitions;
		
	}

	/**
	 * 
	 * @param transition
	 */
	public void addTransition (Transition transition)
	{
		// if it's a State Transition
		if (transition.getClass() == StateTransition.class)
		{
			if (this.stateTransitions.getValue(((StateTransition)transition).getToken().toString()) != null)
			{
				System.out.println(	"  >> State(" + this.id + "): " + (StateTransition)this.stateTransitions.getValue(((StateTransition)transition).getToken().toString()) + " already exists! " +
									"Updating to " + (StateTransition)transition);
			}
				
			this.stateTransitions.put(((StateTransition)transition).getToken().toString(), transition);				
			
		}
		
		// if it's a Submachine Call
		else
		{
			if (this.submachineCalls.getValue(((SubmachineCallTransition)transition).getAutomaton().toString()) != null)
			{
				System.out.println(	"  >> State(" + this.id + "): " + (SubmachineCallTransition)this.submachineCalls.getValue(((SubmachineCallTransition)transition).getAutomaton().toString()) + " already exists! " +
									"Updating to " + (SubmachineCallTransition)transition);
			}
				
			this.submachineCalls.put(((SubmachineCallTransition)transition).getAutomaton().toString(), transition);
		}			
	}
	
	/**
	 * Gets the transition
	 * @param 	tk	token
	 * @return 	a pair <DFA automaton, DFAState nextState>. 
	 * 			if it's a state transition, automaton is null. else if it's a submachine call, then automaton is the submachine
	 */
	public Pair<DFA, DFAState> getNextState (Token tk, Token lookAhead) 				
	{ 
		String tkValue = (tk.tag != Tag.SYMBOL) ? tk.tag.toString().toUpperCase() : tk.toString();
		// verifies if it's a state transition
		if (this.stateTransitions.getValue(tkValue) != null)
		{
			return new Pair<DFA, DFAState>(null, ((StateTransition)this.stateTransitions.getValue(tkValue)).getNextState());
		}
		// verifies if it's a submachine call
		else if (this.getSubmachineCalls().length > 0)
		{
			// if it has only one submachine call
			if (this.getSubmachineCalls().length == 1)
			{
				// verifies if there is a valid transition on the submachine
				if (this.getSubmachineCalls()[0].getAutomaton().getInitialState().getNextState(tk, lookAhead) != null)
				{
					return new Pair<DFA, DFAState>(this.getSubmachineCalls()[0].getAutomaton(), this.getSubmachineCalls()[0].getNextState());
				}
				// if not, syntactic error
				else
				{
					return null;
				}
			}
			
			// non-deterministic submachine call. have to get into all them and see which one we want...  
			else
			{
				Stack<SubmachineCallTransition> smStack = new Stack<SubmachineCallTransition>();				
				for (int i = 0; i < this.getSubmachineCalls().length; i++)
				{
					if (this.getSubmachineCalls()[i].getAutomaton().getInitialState().getNextState(tk, lookAhead) != null)
					{
						smStack.push(this.getSubmachineCalls()[i]);
					}
				}
				
				// no matching submachine found
				if (smStack.getLength() == 0)
				{
					return null;
				}
				
				// found ONE submachine
				else if (smStack.getLength() == 1)
				{
					SubmachineCallTransition temp = smStack.pop();
					return new Pair<DFA, DFAState>(temp.getAutomaton(), temp.getNextState());
				}
				// found more than one submachine. need to look-ahead
				else
				{
					SubmachineCallTransition temp = null;
					SubmachineCallTransition temp2 = null;
					while (!smStack.isEmpty())
					{
						temp = smStack.pop();
						if (temp.getAutomaton().getInitialState().getNextState(tk, lookAhead).getSecond().getNextState(lookAhead, null) != null)
						{
							if (temp2 == null)
							{
								temp2 = temp;
							}
							else
							{
								System.out.println("  >> ERROR! Non-deterministic transition (look-ahead of 1)");
								return null;
							}					
						}
						else if (temp.getAutomaton().getInitialState().getNextState(tk, lookAhead).getSecond().isFinal())
						{
							if (temp2 == null)
							{
								temp2 = temp;
							}
							else
							{
								System.out.println("  >> ERROR! Non-deterministic transition (look-ahead of 1)");
								return null;
							}							
						}
					}
					
					if (temp2 != null)
					{
						return new Pair<DFA, DFAState>(temp2.getAutomaton(), temp2.getNextState());
					}
					else
					{
						System.out.println("ERROR!! And I don't know what else is going on here! =(");
						return null;
					}
				}				 
			}						
		}
		// syntactic error
		else
		{
			return null;
		}
	}
	
	/**
	 * Override ToString
	 */
	public String toString()								
	{ 
		return Integer.toString(this.id);
	}
}
